package com.lagou.edu.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.lagou.edu.annon.*;
import com.lagou.edu.factory.ProxyFactory;

import javax.sql.DataSource;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

public class SpringConfig {
    private static String packageName = "com.lagou.edu";
    private static Map<String,Object> beansMap = new ConcurrentHashMap<>();

    static {

        try {
            // 获取需要扫描注解的包,获取Class
            Set<Class<?>> classSet = getComponentScanClass(packageName);

            // 将Class封装到 beansMap中 key-> beanid, value -> 实体对象
            packageBeanMap(classSet);

            // 处理 autowired按类型注入的问题
            autoWired(classSet);

            // 处理事务时的代理对象问题
            transactional(classSet);

        }catch (Exception ex){
            ex.printStackTrace();
        }

    }

    /**
     * 生成代理对象
     * @param classSet
     * @throws Exception
     */
    public static void transactional(Set<Class<?>> classSet) throws Exception{
        ProxyFactory proxyFactory =(ProxyFactory) getObjFromBeanMap(ProxyFactory.class);

        for (Class clazz: classSet){
            String className = clazz.getName();
            if (clazz.isAnnotationPresent(Transactional.class)){

                String beanId = "";
                Iterator<Map.Entry<String, Object>> iterator = beansMap.entrySet().iterator();
                while (iterator.hasNext()){
                    Map.Entry<String, Object> next = iterator.next();
                    if (className.equals(next.getValue().getClass().getName())){
                        beanId = next.getKey();
                    }
                }

                Object targetObj = getObjFromBeanMap(clazz);
                Object proxyObj = null;
                // 如果是 @Transactional 还需要生成代理对象
                Class[] interfaces = clazz.getInterfaces();
                if (null != interfaces && interfaces.length > 0){
                    // 该类实现了接口, 可以用 jdk动态代理 或 cglib 代理
                    proxyObj = proxyFactory.getJdkProxy(targetObj);
                } else {
                    // 没有实现接口, 必须使用 cglib 代理
                    proxyObj = proxyFactory.getCglibProxy(targetObj);
                }
                beansMap.put(beanId,proxyObj);
            }
        }
    }

    /**
     * 自动注入
     * @param classSet
     * @throws Exception
     */
    public static void autoWired(Set<Class<?>> classSet) throws Exception{
        if (classSet.isEmpty()) return;

        for (Class clazz: classSet){

            Object parentObj = getObjFromBeanMap(clazz);

            Field[] fields = clazz.getDeclaredFields();

            for (Field field: fields){
                field.setAccessible(true);

                Autowired declaredAnnotation = field.getDeclaredAnnotation(Autowired.class);
                // 如果是自动注入的类型
                if (null != declaredAnnotation){
                    Class<?> fieldTypeClass = field.getType();
                    // 从BeanMap中获取
                    Object obj = getObjFromBeanMap(fieldTypeClass);
                    // 如果可以获取到的话
                    if (null != obj){
                        System.out.println(clazz+"\t"+obj);
                    } else {
                        //获取不到,就从第三方新建，获取第三方的OBJ
                        obj = fieldTypeClass.newInstance();
                    }

                    if (obj instanceof DataSource){
                        DruidDataSource druidDataSource = (DruidDataSource)obj;
                        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
                        druidDataSource.setUrl("jdbc:mysql://localhost:3306/bank");
                        druidDataSource.setUsername("root");
                        druidDataSource.setPassword("1234");
                    }

                    field.set(parentObj,obj);
                }
            }
        }
    }

    /**
     * 获取需要扫描注解的包,获取Class
     *
     * @param packageName
     * @return
     * @throws Exception
     */
    public static  Set<Class<?>> getComponentScanClass(String packageName) throws Exception{
        Set<Class<?>> classSet = new HashSet<>();

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String packagePathrRep = packageName.replace(".", "/");
        Enumeration<URL> resources = classLoader.getResources(packagePathrRep);

        while (resources.hasMoreElements()){
            URL url = resources.nextElement();
            if (null == url) continue;

            String protocol = url.getProtocol();
            if ("file".equalsIgnoreCase(protocol)){
                String packagePath = url.getPath().replaceAll("%20", " ");
                addClass(classSet,packagePath,packageName);
            }
        }
        return classSet;
    }

    /**
     * 根据class从beansMap中获取对应的实例对象
     *
     * @param clazz
     * @return
     * @throws Exception
     */
    public static Object getObjFromBeanMap(Class<?> clazz) throws Exception{
        Iterator<Map.Entry<String, Object>> iterator = beansMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<String, Object> next = iterator.next();
            Object value = next.getValue();
            if (clazz.isInterface()){
                Class<?>[] interfaces = value.getClass().getInterfaces();
                if (null != interfaces && interfaces.length > 0){
                    Optional<Class<?>> first = Stream.of(interfaces).filter(item -> item.getName().equalsIgnoreCase(clazz.getName())).findFirst();
                    if (first.isPresent()){
                        return value;
                    }
                }
            } else if (value.getClass().getName().equalsIgnoreCase(clazz.getName())){
                return value;
            }
        }
        return null;
    }

    /**
     * 封装beansMap
     *
     * @param classSet
     * @throws Exception
     */
    private static void packageBeanMap(Set<Class<?>> classSet) throws Exception{
        for (Class clazz: classSet){
            String className = clazz.getSimpleName();
            String beanId = Character.toLowerCase(className.charAt(0)) + className.substring(1);
            Object objValue = clazz.newInstance();

            if (clazz.isAnnotationPresent(Component.class)){
                Component component =  (Component)clazz.getAnnotation(Component.class);
                if (null != component.value() && !"".equalsIgnoreCase(component.value())) {
                    beanId = component.value();
                }
            } else if (clazz.isAnnotationPresent(Repository.class)){
                Repository repository =  (Repository)clazz.getAnnotation(Repository.class);
                if (null != repository.value() && !"".equalsIgnoreCase(repository.value())) {
                    beanId = repository.value();
                }
            }else if (clazz.isAnnotationPresent(Service.class)){
                Service service =  (Service)clazz.getAnnotation(Service.class);
                if (null != service.value() && !"".equalsIgnoreCase(service.value())) {
                    beanId = service.value();
                }
            }
            beansMap.put(beanId,objValue);
        }
    }

    private static void addClass(Set<Class<?>> classSet,String packagePath,String packageName){
        File[] files = new File(packagePath).listFiles(
                file -> file.isFile() && file.getName().endsWith(".class") || file.isDirectory()
        );

        Stream.of(files).forEach(file -> {
            String fileName = file.getName();
            if (file.isFile()){
                String className = fileName.substring(0, fileName.lastIndexOf("."));
                if (!"".equalsIgnoreCase(packageName)){
                    className = packageName+"."+className;
                }
                doAddClass(classSet,className);
            } else {
                String subPackagePath = fileName;
                if (!"".equalsIgnoreCase(packagePath)){
                    subPackagePath = packagePath + "/" + subPackagePath;
                }
                String subPackageName = fileName;
                if (!"".equalsIgnoreCase(packageName)){
                    subPackageName = packageName + "." + subPackageName;
                }
                addClass(classSet,subPackagePath,subPackageName);
            }

        });
    }

    /**
     * 加载类
     */
    public static void doAddClass(Set<Class<?>> classSet,String className) {
        Class<?> cls;
        try {
            cls = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        // 注解类不会添加进去
        if (cls.isAnnotation()){
            return;
        }

        if(cls.getName().equals(SpringConfig.class.getName())){
            return ;
        }
        Set<Class<?>> annotationsList = new HashSet<>();

        Annotation[] declaredAnnotations = cls.getDeclaredAnnotations();
        if (null != declaredAnnotations && declaredAnnotations.length > 0) {
            for (Annotation annotation : declaredAnnotations) {
                getAnno(annotationsList,annotation,false);
            }
        }

        // 查询是否含有@Component注解
        boolean isAdd = annotationsList.stream().anyMatch(item -> Component.class.getName().equals(item.getName()));

        if (isAdd){
            classSet.add(cls);
        }

    }


    /**
     * 递归 获取类上的注解 以及 注解类上的注解
     * @param annotationsList
     * @param annotation
     * @param isConntionue
     */
    public static void getAnno(Set<Class<?>> annotationsList,Annotation annotation,boolean isConntionue){
        annotationsList.add(annotation.annotationType());
        if (isConntionue) return;
        Annotation[] annotations =  annotation.annotationType().getAnnotations();

        for (Annotation annotationItem: annotations){
            getAnno(annotationsList,annotationItem,Stream.of(annotations).anyMatch(item -> item.annotationType().getName().equals(annotation.annotationType().getName())));
        }
    }
}
